package com.test.drawing;

import java.util.Arrays;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;

/**
 * BoardMesh of an OpenGL Model which draws a Billboard in the OpenGL
 * Surface.
 */
public class BoardMesh extends Mesh {

	private final float[] COLOR = new float[] { 255f, 255f, 255f, 1f };
	private static final int TEXT_SIZE = 180;
	private static final int BITMAP_WIDTH = 256, BITMAP_HEIGHT = 256;
	private static final int[] TEXT_POS = new int[] { BITMAP_WIDTH / 2,
			BITMAP_HEIGHT / 2 + TEXT_SIZE / 3 };

	private static final float FADE_FACTOR = 0.5f;
	private static final float FADE_OUT_FACTOR = 0.1f;

	private boolean isFading = false;
	private boolean stopingFading = false;
	private android.graphics.drawable.Drawable background;
	
	// Last Computed Distances
	private float[] lastMeshComputedPosition = {0, 0 ,0};
	private float[] lastCameraComputedPosition = {0, 0 ,0};
	
	// BillBoard Rotations
	private float[] firstRotationUpAux = {0, 0, 0};
	private float firstRotationAngle = 0f;
	private float[] secondRotationUpAux = {0, 0, 0};
	private float secondRotationAngle = 0f;

	/**
	 * BoardMesh Constructor<br>
	 * Creates a Mesh that is faced to the OpenGLCamera
	 * 
	 * @param signScene
	 *            Scene to be used
	 * @param drawable
	 *            Background of the Board's Texture
	 */
	public BoardMesh(MeshBuffer meshBuffer,
			android.graphics.drawable.Drawable drawable) {
		super(meshBuffer);
		this.background = drawable;
		
		// Sets the Scale to 0.5 from original size
		setScale(0.55f);
	}

	private void setScale(float f) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * Loads a new Texture using an bitmap containing the given number
	 * 
	 * @param number
	 *            Number to be Written in the Texture
	 */
	public void setNumber(int number) {
		loadBitmap(createBitmap(String.valueOf(number), background));
	}

	/*
	 * Draws the given String into the Background
	 */
	private Bitmap createBitmap(String number,
			android.graphics.drawable.Drawable background) {
		// Create an empty, mutable bitmap
		Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT,
				Bitmap.Config.ARGB_4444);
		// get a canvas to paint over the bitmap
		Canvas canvas = new Canvas(bitmap);
		bitmap.eraseColor(0);

		// note the image format must match the bitmap format
		background.setBounds(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
		background.draw(canvas); // draw the background to our bitmap

		// Draw the text
		Paint textPaint = new Paint();
		textPaint.setTextAlign(Paint.Align.CENTER);
		textPaint.setTextSize(TEXT_SIZE);
		textPaint.setAntiAlias(true);
		textPaint.setARGB(255, 255, 255, 255);
		// draw the text centered
		canvas.drawText(number, TEXT_POS[0], TEXT_POS[1], textPaint);
		return bitmap;
	}

	/**
	 * Fades the Mesh to 1.0 Alpha
	 */
	public void startFading() {
		if (!isFading && !stopingFading && COLOR[3] == 1) {
			isFading = true;
		}
	}

	/**
	 * Fades out the Mesh to 0.0 Alpha
	 */
	public void stopFading() {
		if (!isFading && !stopingFading && COLOR[3] == 0) { // Not Invisible
			stopingFading = true;
		}
	}

	@Override
	public void draw(GL10 gl) {
		fade(gl);

		setPointers(gl);

		gl.glPushMatrix();
		
		gl.glTranslatef(getX(), getY(), getZ());

		billboard(gl);
		
		drawElements(gl);

		gl.glPopMatrix();
	}

	private void setPointers(GL10 gl) {
		
	}

	// Sets the Color to Fade or Fade out
	private void fade(GL10 gl) {

		if (isFading) {
			
			COLOR[3] = COLOR[3] - FADE_FACTOR;
			setColor(COLOR[0], COLOR[1], COLOR[2], COLOR[3]);

			if (COLOR[3] <= 0) {
				COLOR[3] = 0;
				isFading = false;
			}

		} else {
			if (stopingFading) {
				
				COLOR[3] = COLOR[3] + FADE_OUT_FACTOR;
				setColor(COLOR[0], COLOR[1], COLOR[2], COLOR[3]);
				
				if (COLOR[3] >= 1) {
					COLOR[3] = 1;
					stopingFading = false;
				}
			}
		}
	}
	
	// Prepare the OpenGL Interface
	// to Draw Objects using the Billboard effect
	private void billboard(GL10 gl) {
		
		// If any position has changed, then calculate again
		if (!(Arrays.equals(lastCameraComputedPosition, OpenGLCamera.getPosition())
				&& Arrays.equals(getPosition(), lastMeshComputedPosition)))
			calculateBillboard();
		
		// First Rotation
		gl.glRotatef(firstRotationAngle,
				firstRotationUpAux[0], firstRotationUpAux[1], firstRotationUpAux[2]);
		
		// Second Rotation
		gl.glRotatef(secondRotationAngle,
				secondRotationUpAux[0], secondRotationUpAux[1], secondRotationUpAux[2]);
	}

	private void calculateBillboard() {
		
		float[] camPos = OpenGLCamera.getPosition();

		float[] lookAt = new float[3];
		float[] objToCamProj = new float[3];
		float[] objToCam = new float[3];
		float[] upAux = new float[3];
		float angleCosine;

		// objToCamProj is the vector in world coordinates from the
		// local origin to the camera projected in the XZ plane
		objToCamProj[0] = camPos[0] - getX();
		objToCamProj[1] = 0;
		objToCamProj[2] = camPos[2] - getZ();

		// correction
		// objToCamProj[0] = objToCamProj[0] == 0 ? 1 : objToCamProj[0];

		// This is the original lookAt vector for the object
		// in world coordinates
		lookAt[0] = 0;
		lookAt[1] = 0;
		lookAt[2] = 1;

		// normalize both vectors to get the cosine directly afterwards
		mathsNormalize(objToCamProj);

		// easy fix to determine wether the angle is negative or positive
		// for positive angles upAux will be a vector pointing in the
		// positive y direction, otherwise upAux will point downwards
		// effectively reversing the rotation.

		mathsCrossProduct(upAux, lookAt, objToCamProj);

		// compute the angle
		angleCosine = mathsInnerProduct(lookAt, objToCamProj);

		// perform the rotation. The if statement is used for stability reasons
		// if the lookAt and objToCamProj vectors are too close together then
		// |angleCosine| could be bigger than 1 due to lack of precision
		
		// may be needed to rotate only if
		// angleCosine < 0.99990 && angleCosine > -0.9999
		firstRotationAngle = (float) (Math.acos(angleCosine) * 180 / 3.14);
		firstRotationUpAux = upAux;
			
			
		// so far it is just like the cylindrical billboard. The code for the
		// second rotation comes now
		// The second part tilts the object so that it faces the camera

		// objToCam is the vector in world coordinates from
		// the local origin to the camera
		objToCam[0] = camPos[0] - getX();
		objToCam[1] = camPos[1] - getY();
		objToCam[2] = camPos[2] - getZ();

		// Normalize to get the cosine afterwards
		mathsNormalize(objToCam);

		// Compute the angle between objToCamProj and objToCam,
		// i.e. compute the required angle for the lookup vector

		angleCosine = mathsInnerProduct(objToCamProj, objToCam);

		// Tilt the object. The test is done to prevent instability
		// when objToCam and objToCamProj have a very small
		// angle between them
		
		// may be needed to rotate only if
		// angleCosine < 0.99990 && angleCosine > -0.9999
		
		secondRotationAngle = (float) (Math.acos(angleCosine) * 180 / 3.14);
		
			if (objToCam[1] < 0) {
				secondRotationUpAux = new float[] {1, 0, 0};
			} else {
				secondRotationUpAux = new float[] {-1, 0, 0};
			}
	}

	private void mathsCrossProduct(float[] a, float[] b, float[] c) {
		a[0] = b[1] * c[2] - c[1] * b[2];
		a[1] = b[2] * c[0] - c[2] * b[0];
		a[2] = b[0] * c[1] - c[0] * b[1];

	}

	private float mathsInnerProduct(float[] v, float[] q) {
		return v[0] * q[0] + v[1] * q[1] + v[2] * q[2];
	}

	// Normalize the Vector
	private void mathsNormalize(float[] v) {
		float d = (float) (Math.sqrt((v[0] * v[0]) + (v[1] * v[1])
				+ (v[2] * v[2])));
		v[0] = v[0] / d;
		v[1] = v[1] / d;
		v[2] = v[2] / d;
	}
}
